/*
* Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
* All rights reserved.
* This component and the accompanying materials are made available
* under the terms of "Eclipse Public License v1.0"
* which accompanies this distribution, and is available
* at the URL "http://www.eclipse.org/legal/epl-v10.html".
*
* Initial Contributors:
* Nokia Corporation - initial contribution.
*
* Contributors:
*
* Description:
*/

#include <aknglobalnote.h> // CAknGlobalNote.
#include <apacmdln.h>      // CApaCommandLine.
#include <apgcli.h>        // RApaLsSession.
#include <apgtask.h>       // TApaTaskList, TApaTask.
#include <avkon.rsg>       // R_AVKON_SOFTKEYS_OK_EMPTY.
#include <ndefmessage.h>   // CNdefMessage.
#include <s32mem.h>        // RDesWriteStream.

#include "constants.hrh"
#include "messagehandler.h"

// Type-safe versions of the values defined in the header constants.hrh.
const TUid KApplicationUid = { KContentHandlerApplicationExeUid };

// The name of the global semaphore used to wait for application startup.
_LIT( KSemaphoreName, "nfccontenthandlersemaphore" );

// The name of the global mutex used used to protect access to the shared memory chunk.
_LIT( KMutexName, "nfccontenthandlermutex" );

// The name of the global shared memory chunk.
_LIT( KChunkName, "nfccontenthandlerchunk" );

// The requested size of the shared memory chunk in bytes. The actual allocated
// size may be larger, since chunk size values are rounded up to the next nearest
// processor page boundary value, typically 4 kilobytes.
const TInt KChunkSize = 1024;

// The maximum time to wait for the application to open the global handles.
// 10 seconds in milliseconds.
const TInt KSemaphoreTimeoutMs = 10000000;

// The size, in bytes, of the header information written to the shred memory chunk
// in addition to the NDEF message: one TInt64 for a time stamp value and one
// TInt for the size of the NDEF message.
const TInt KSharedMemoryHeaderSize = sizeof( TInt64 ) + sizeof( TInt );

// ---------------------------------------------------------------------------
// Symbian two-phased constructor.
// ---------------------------------------------------------------------------
//
CMessageHandler* CMessageHandler::NewL()
    {
    CMessageHandler* self = new (ELeave) CMessageHandler;
    CleanupStack::PushL( self );
    self->ConstructL();
    CleanupStack::Pop( self );

    return self;
    }

// ---------------------------------------------------------------------------
// C++ destructor.
// ---------------------------------------------------------------------------
//
CMessageHandler::~CMessageHandler()
    {
    Cancel();

    delete iMessage;
    delete iGlobalNote;

    iMutex.Close();
    iChunk.Close();
}

// ---------------------------------------------------------------------------
// Pre-handling for the NDEF message. Completes the request on errors.
// ---------------------------------------------------------------------------
//
void CMessageHandler::HandleNdefMessage(
    TRequestStatus& aRequestStatus,
    CNdefMessage* aMessage )
    {
    // This class can only handle a single NDEF message at a time. If a new
    // message is supplied while the previous one is still being processed,
    // flag the situation as an error by leaving with KErrInUse.
    if ( !IsActive() )
        {
        // We are assuming ownership of the NDEF message passed in, so the first
        // thing to do is to store the message pointer. The message is guaranteed
        // to be deleted in CompleteRequest() whether or not this handler completes
        // successfully.
        iMessage = aMessage;

        iRequestStatus = &aRequestStatus;
        *iRequestStatus = KRequestPending;

        // Sanity check the NDEF message. There should be at least one record
        // since a custom record type was identified in the message and sent
        // to this class for handling in the first place. Just to be on the
        // safe side, flag an empty message passed this far as corrupted.
        if ( aMessage->Count() > 0 )
            {
            SetActiveState( EStateWriteSharedMemory, ETrue );
            }
        else
            {
            CompleteRequest( KErrCorrupt );
            }
        }
    else
        {
        // Delete the message passed in as a parameter, as the content handler
        // assumes we will take its ownership.
        delete aMessage;
        aMessage = NULL;

        // Here we have to use User::RequestComplete() instead of the
        // convenience function CMessageHandler::CompleteRequest() since
        // IsActive() returned ETrue, the iRequestStatus member variable has
        // already been set to a value other than the aRequestStatus supplied
        // to this function.
        TRequestStatus* requestStatus = &aRequestStatus;
        User::RequestComplete( requestStatus, KErrInUse );
        }
    }

// ---------------------------------------------------------------------------
// From base class CActive, cancels any ongoing message handling.
// ---------------------------------------------------------------------------
//
void CMessageHandler::DoCancel()
    {
    // If the identifier of the global note is non-zero, this handler is
    // currently showing a note. Cancel it before cancelling the pending
    // NFC Contect Handler request.
    if ( iGlobalNoteId )
        {
        TRAP_IGNORE( iGlobalNote->CancelNoteL(iGlobalNoteId) );
        }

    CompleteRequest( KErrCancel );
    }

// ---------------------------------------------------------------------------
// From base class CActive, processes the NDEF message in asynchronous steps.
// ---------------------------------------------------------------------------
//
void CMessageHandler::RunL()
    {
    User::LeaveIfError( iStatus.Int() );

    switch ( iState )
        {
        case EStateWriteSharedMemory:
            WriteSharedMemoryL();
            break;
        case EStateStartApplication:
            StartApplicationL();
            break;
        case EStateWaitForApplicationToStartUp:
            WaitForApplicationToStartUpL();
            break;
        default:
            // Unrecognized state, complete the pending request with an error.
            CompleteRequest( KErrAbort );
            break;
        }
    }

// ---------------------------------------------------------------------------
// From base class CActive, completes the handler request with an error code.
// ---------------------------------------------------------------------------
//
TInt CMessageHandler::RunError( TInt aError )
    {
    // Show a global error note describing the state of this message handler
    // as it was whn the error occurred.
    TRAP_IGNORE( ShowGlobalNoteL(aError) )

    // Complete the pending request with the code of the error that took place.
    // Note that in practice RunError() will never get called, as RunL()
    // contains no potentially leaving calls.
    CompleteRequest( aError );

    // Return KErrNone to the active scheduler to indicate that the error
    // situation has been handled gracefully.
    return KErrNone;
    }

// ---------------------------------------------------------------------------
// C++ constructor.
// ---------------------------------------------------------------------------
//
CMessageHandler::CMessageHandler() : CActive( CActive::EPriorityLow )
    {
    CActiveScheduler::Add( this );

    OpenMutex();
    OpenChunk();
    }

// ---------------------------------------------------------------------------
// Symbian second phase constructor.
// ---------------------------------------------------------------------------
//
void CMessageHandler::ConstructL()
    {
    iGlobalNote = CAknGlobalNote::NewL();
    iGlobalNote->SetSoftkeys( R_AVKON_SOFTKEYS_OK_EMPTY );
    }

// ---------------------------------------------------------------------------
// Opens or creates the global mutex used to protect access to the memory chunk.
// ---------------------------------------------------------------------------
//
void CMessageHandler::OpenMutex()
    {
    // Depending on which was started first, the plugin running this message
    // handler, or the application, the global mutex may already exist. First
    // try to open the mutex (meaning the application has already created it),
    // and if it does not exist, try creating it (meaning the plugin code was
    // run, but the application is not running yet).
    iMutexError = iMutex.OpenGlobal( KMutexName );

    if ( iMutexError != KErrNone )
        {
        iMutexError = iMutex.CreateGlobal( KMutexName );
        }
    }

// ---------------------------------------------------------------------------
// Opens or creates the global shared memory chunk.
// ---------------------------------------------------------------------------
//
void CMessageHandler::OpenChunk()
    {
    // Try to open or create the global shared memory chunk. The discussion
    // about plugin and application startup order in OpenMutex() above applies
    // equally to the shared chunk.
    iChunkError = iChunk.OpenGlobal( KChunkName, EFalse );

    if ( iChunkError != KErrNone )
        {
        iChunkError = iChunk.CreateGlobal( KChunkName, KChunkSize, KChunkSize );

        if ( iChunkError == KErrNone )
            {
            TPtr8 chunkPtr( iChunk.Base(), iChunk.Size() );
            chunkPtr.FillZ( chunkPtr.MaxSize() );
            }
        }
    }

// ---------------------------------------------------------------------------
// Displays a global error note with debug values for this message handler.
// ---------------------------------------------------------------------------
//
void CMessageHandler::ShowGlobalNoteL( TInt aError )
    {
    TBuf<128> buffer;

    buffer.Append( _L("aError ") );
    buffer.AppendNum( aError );

    buffer.Append( _L(", iState ") );
    buffer.AppendNum( iState );

    buffer.Append( _L(", iSemaphoreError ") );
    buffer.AppendNum( iSemaphoreError );

    buffer.Append( _L(", iChunkError ") );
    buffer.AppendNum( iChunkError );

    buffer.Append( _L(", iMutexError ") );
    buffer.AppendNum( iMutexError );

    if ( iChunkError == KErrNone )
        {
        buffer.Append( _L(", iChunk.Size() ") );
        buffer.AppendNum( iChunk.Size() );
        buffer.Append( _L(", iChunk.MaxSize() ") );
        buffer.AppendNum( iChunk.MaxSize() );
        }

    iGlobalNoteId = iGlobalNote->ShowNoteL( EAknGlobalErrorNote, buffer );
}

// ---------------------------------------------------------------------------
// Writes a time stamp and the size of the NDEF message to a buffer.
// ---------------------------------------------------------------------------
//
TInt CMessageHandler::WriteSharedMemoryHeaderL( TPtr8 aChunkPtr )
    {
    TInt bytesWritten = 0;
    aChunkPtr.Zero();

    if ( iMessage && (KSharedMemoryHeaderSize <= aChunkPtr.MaxSize()) )
        {
        RDesWriteStream writeStream( aChunkPtr );
        CleanupClosePushL( writeStream );

        TTime time;
        time.HomeTime();
        const TInt64 timeInt64 = time.Int64();

        writeStream.WriteUint32L( I64HIGH(timeInt64) );
        writeStream.WriteUint32L( I64LOW(timeInt64) );
        writeStream.WriteInt32L( iMessage->SizeL() );

        CleanupStack::PopAndDestroy( &writeStream );

        bytesWritten = KSharedMemoryHeaderSize;
        }

    return bytesWritten;
    }

// ---------------------------------------------------------------------------
// Writes the entire NDEF message to a buffer.
// ---------------------------------------------------------------------------
//
TInt CMessageHandler::WriteSharedMemoryMessageL( TPtr8 aChunkPtr )
    {
    TInt bytesWritten = 0;
    aChunkPtr.Zero();

    if ( iMessage && (iMessage->SizeL() <= aChunkPtr.MaxSize()) )
        {
        bytesWritten = iMessage->ExportRawDataL( aChunkPtr, 0 );
        }

    return bytesWritten;
    }

// ---------------------------------------------------------------------------
// Writes header information and the entire NDEF message to shared memory.
// ---------------------------------------------------------------------------
//
void CMessageHandler::WriteSharedMemoryL()
    {
    const TInt messageSize = iMessage->SizeL();
    TInt bytesWritten = 0;

    if ( (iMutexError == KErrNone) && (iChunkError == KErrNone) )
        {
        const TInt chunkSize = iChunk.Size();

        // Make sure the shared memory chunk we allocated is large enough to
        // contain both the entire message and the header information.
        if ( (messageSize + KSharedMemoryHeaderSize) <= chunkSize )
            {
            iMutex.Wait();

            // Use a TCleanupItem to signal the mutex. This ensure that the mutex
            // is signalled even if a leave occurs between the RMutex::Wait() and
            // RMutex::Signal() calls.
            CleanupStack::PushL( TCleanupItem(&CMessageHandler::CleanupSignal, reinterpret_cast<TAny*>(&iMutex)) );

            TPtr8 chunkHeaderPtr( iChunk.Base(), KSharedMemoryHeaderSize );
            TPtr8 chunkMessagePtr( iChunk.Base() + KSharedMemoryHeaderSize, chunkSize - KSharedMemoryHeaderSize );

            bytesWritten += WriteSharedMemoryHeaderL( chunkHeaderPtr );
            bytesWritten += WriteSharedMemoryMessageL( chunkMessagePtr );

            CleanupStack::PopAndDestroy(); // The TCleanupItem cleanup operation will call iMutex.Signal().
            }
        else
            {
            User::Leave( KErrTooBig );
            }
        }

    // Raise an error if all of the message was not exported, as in this case
    // the application will be able unable to decode the message.
    if ( bytesWritten < (KSharedMemoryHeaderSize + messageSize) )
        {
        User::Leave( KErrCorrupt );
        }

    SetActiveState( EStateStartApplication, ETrue );
    }

// ---------------------------------------------------------------------------
// Starts the application that will handle the NDEF message passed to it.
// ---------------------------------------------------------------------------
//
void CMessageHandler::StartApplicationL()
    {
    RWsSession wsSession;
    User::LeaveIfError( wsSession.Connect() );
    CleanupClosePushL( wsSession );

    TApaTaskList apaTaskList( wsSession );
    TApaTask apaTask = apaTaskList.FindApp( KApplicationUid );

    if ( apaTask.Exists() )
        {
        apaTask.BringToForeground();
        CompleteRequest( KErrNone );
        }
    else
        {
        RApaLsSession apaLsSession;
        User::LeaveIfError( apaLsSession.Connect() );
        CleanupClosePushL( apaLsSession );

        TApaAppInfo apaAppInfo;
        User::LeaveIfError( apaLsSession.GetAppInfo(apaAppInfo, KApplicationUid) );

        CApaCommandLine* apaCommandLine = CApaCommandLine::NewLC();
        apaCommandLine->SetExecutableNameL( apaAppInfo.iFullName );
        apaCommandLine->SetCommandL( EApaCommandRun );

        User::LeaveIfError( apaLsSession.StartApp(*apaCommandLine) );

        CleanupStack::PopAndDestroy(2, &apaLsSession); // apaCommandLine, apaLsSession.

        SetActiveState( EStateWaitForApplicationToStartUp, ETrue );
    }

    CleanupStack::PopAndDestroy( &wsSession );
    }

// ---------------------------------------------------------------------------
// Waits for the application to signal it has finished starting up.
// ---------------------------------------------------------------------------
//
void CMessageHandler::WaitForApplicationToStartUpL()
    {
    RSemaphore semaphore;
    iSemaphoreError = semaphore.CreateGlobal( KSemaphoreName, 0 );

    if ( iSemaphoreError == KErrNone )
        {
        iSemaphoreError = semaphore.Wait( KSemaphoreTimeoutMs );

        if ( iSemaphoreError == KErrNone )
            {
            CompleteRequest( KErrNone );
            }
        }

    semaphore.Close();

    User::LeaveIfError( iSemaphoreError );
    }

// ---------------------------------------------------------------------------
// Moves into a new asynchronous state and optionally completes the request.
// ---------------------------------------------------------------------------
//
void CMessageHandler::SetActiveState( TInt aState, TBool aCompleteRequest )
    {
    iState = aState;
    SetActive();

    if ( aCompleteRequest )
        {
        TRequestStatus* requestStatus = &iStatus;
        User::RequestComplete( requestStatus, KErrNone );
        }
    }

// ---------------------------------------------------------------------------
// Utility function to complete the NFC Content Handler request.
// ---------------------------------------------------------------------------
//
void CMessageHandler::CompleteRequest( TInt aError )
    {
    // If the iRequestStatus member variable has not been set, there is nothing
    // to complete. This situation is ignored silently, as it's not harmful.
    if ( iRequestStatus && (*iRequestStatus == KRequestPending) )
        {
        // The NDEF message has been handled, successfully or otherwise. Signal
        // this by completing the NFC Content Handler's pending request.
        User::RequestComplete( iRequestStatus, aError );

        // Reset this message handler instance to its initial state where it is
        // ready to receive a new message.
        iState = EStateIdle;
        iRequestStatus = NULL;
        iGlobalNoteId = 0;

        // We are done with the NDEF message, delete it as its ownership was
        // passed to us when HandleNdefMessage() was called.
        delete iMessage;
        iMessage = NULL;
        }
    }

// ---------------------------------------------------------------------------
// Cleanup operation to signal mutexes on being pop & destroyed.
// ---------------------------------------------------------------------------
//
void CMessageHandler::CleanupSignal( TAny* aAny )
    {
    if ( aAny )
        {
        RMutex* mutex = reinterpret_cast<RMutex*>( aAny );
        mutex->Signal();
        }
    }
